import {
  Row, Col, Button, Input, Space, Dropdown, Slider, ColorPicker,
  InputNumber, Flex, Select, Spin, message,
  Tag, Tooltip, Typography,
} from "antd";
import Icon, {
  PlusOutlined, MinusOutlined,
} from "@ant-design/icons";
import { useState, useRef, useEffect, useCallback, useMemo } from "react";
import CaijianIcon from "./assets/svgs/caijian.svg?react";
import HuabiIcon from "./assets/svgs/huabi.svg?react";
import WenziIcon from "./assets/svgs/wenzi.svg?react";
import XuanzhuanIcon from "./assets/svgs/xuanzhuan.svg?react";
import YijianchongzhiIcon from "./assets/svgs/yijianchongzhi.svg?react";
import ChexiaoIcon from "./assets/svgs/chexiao.svg?react";
import HuifuIcon from "./assets/svgs/huifu.svg?react";
import GuanlianIcon from "./assets/svgs/guanlian.svg?react";
import "./App.less";
import { cloneDeep, isEqual } from "lodash-es";
import MyTooltip from "./components/MyTooltip";
import ToolButton from "./components/toolButton";
import edit_icon01_png from "./assets/icon/edit-icon01.png";
import edit_icon02_png from "./assets/icon/edit-icon02.png";
import edit_icon03_png from "./assets/icon/edit-icon03.png";
import sketch_bg_png from "./assets/sketch-bg.png";
import font_png from "./assets/icon/font.png";
import CopyIcon from "./assets/svgs/ie_copy.svg?react";
import ie_text_option_01_png from "./assets/icon/ie_text_option_01.png";
import ie_text_option_02_png from "./assets/icon/ie_text_option_02.png";
import ie_rotate_option_01_png from "./assets/icon/ie_rotate_option_01.png";
import ie_rotate_option_02_png from "./assets/icon/ie_rotate_option_02.png";
import ie_reset_option_01_png from "./assets/icon/ie_reset_option_01.png";
import ie_reset_option_02_png from "./assets/icon/ie_reset_option_02.png";
import { type IActiveType, type IModalType } from "./types";
import {
  DEFAULT_TEXT_CONFIG,
  FONT_FAMILY_OPTIONS,
  FONT_SIZE_OPTIONS,
  OBJECT_ID,
  ROTATE_IMG,
} from "./data";
import {
  renderSvgIcon,
  fabric,
  fabricWithErasing,
  calcImgScaleFn,
  _getPresetPropertiesForCropSize,
} from "./kit";
import {
  sysFileDownloadApi,
} from "./api";
import MyTooltipButton from "./components/MyTooltipButton";
import type { IEvent } from "fabric/fabric-impl";

fabric.Object.prototype.toObject = (function (toObject) {
  return function (propertiesToInclude) {
    // @ts-ignore
    return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
      // @ts-ignore
      ...Object.keys(this).reduce((res, key) => {
        if (
          ["id", "selectable", "hoverCursor", "isBatch", "textId"].includes(key)
        ) {
          // @ts-ignore
          res[key] = this[key];
        }
        return res;
      }, {}),
    });
  };
})(fabric.Object.prototype.toObject);

export default function App() {
  const historyMap = useRef<
    Record<
      string,
      {
        undoStacks: Array<Record<string, any>>;
        redoStacks: Array<Record<string, any>>;
      }
    >
  >({});
  const [eraserWidth, setEraserWidth] = useState(12);
  const isDrawingModeRef = useRef(false);
  const [tempDrawObjects, setTempDrawObjects] = useState<Array<fabric.Object>>(
    []
  );
  const [modalInfo, setModalInfo] = useState<Record<string, any>>({});
  const [isOpenMap, setIsModalOpenMap] = useState<
    Partial<{
      [key in IModalType]: boolean;
    }>
  >({});
  const [isActiveMap, setIsActiveMap] = useState<
    Partial<{
      [key in IActiveType]: boolean;
    }>
  >({});
  const [strokeWidth, setStrokeWidth] = useState(12);
  const [strokeColor, setStrokeColor] = useState("#000000");
  const [textConfig, setTextConfig] =
    useState<Record<string, any>>(DEFAULT_TEXT_CONFIG);
  const isDoing = useRef(false);
  const isSaving = useRef(false);
  const isReset = useRef(false);
  const [imageList, setImageList] = useState<Array<Record<string, any>>>([]);
  const [activeImage, setActiveImage] = useState<Record<string, any>>({});
  const [canvasInfo, setCanvasInfo] = useState<
    Record<string, Record<string, any>>
  >({});
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const cacheImgRef = useRef<Record<string, any>>({});
  const [isSpinning, setIsSpinning] = useState(true);
  const [isUseRatio, setIsUseRatio] = useState(false);
  const [cropRatio, setCropRatio] = useState("");
  const isSyncCanvas = useRef(false);
  const isUpdateCanvas = useRef(false);
  const canvasInfoRef = useRef<Record<string, any>>({});
  const [canvasMap, setCanvasMap] = useState<Record<string, fabric.Canvas>>({});
  const [imgDesMap, setImgDesMap] = useState<Record<string, string>>({});
  const isUpdateDesc = useRef(false);
  const isSpacePressedRef = useRef(false);
  const isDraggingRef = useRef(false);
  const lastPosRef = useRef({ x: 0, y: 0 });
  const syncCanvasIdRef = useRef(-1);
  const batchTextRecordRef = useRef<Record<string, Array<string>>>({});

  const init = () => {
    initObjectPrototype();
    renderRotateController();

    setIsSpinning(true);
    getPictureInfoFn().finally(() => {
      setTimeout(() => {
        setIsSpinning(false);
      }, 1000);
    });
  };

  useEffect(() => {
    init();
  }, []);

  useEffect(() => {
    if (Array.isArray(imageList) && imageList.length) {
      const promise_list: any[] = [];
      imageList.forEach((item) => {
        const id = item?.id;
        const target = item?.canvasRef?.current;
        if (id && target) {
          promise_list.push(
            initCanvasFn({
              imgInfo: item,
              ref: target,
            })
          );
        }
      });

      Promise.all(promise_list).then((result) => {
        if (!Array.isArray(result) || !result.length) return;

        const canvasMapRef_new = {};
        result.forEach((canvas, index) => {
          const id = imageList?.[index]?.id;
          if (id && canvas) {
            Object.assign(canvasMapRef_new, {
              [id]: canvas,
            });
          }
        });

        setCanvasMap(canvasMapRef_new);
        setCanvasInfo({ ...canvasInfoRef?.current });
        canvasInfoRef.current = {};
      });
    }
  }, [imageList]);

  useEffect(() => {
    if (isOpenMap?.isEraser) {
      const canvas = getCurrentCanvasFn();
      if (canvas) {
        canvas.freeDrawingBrush = new fabricWithErasing.EraserBrush(canvas);
        canvas.freeDrawingBrush.width = eraserWidth;
      }
    }
  }, [eraserWidth, isOpenMap?.isEraser]);

  useEffect(() => {
    if (!isOpenMap?.isEraser) {
      const canvas = getCurrentCanvasFn();
      if (canvas) {
        canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
        canvas.freeDrawingBrush.width = strokeWidth;
        canvas.freeDrawingBrush.color = strokeColor;
      }
    }
  }, [strokeWidth, strokeColor, isOpenMap?.isEraser]);

  useEffect(() => {
    if (textConfig) {
      const canvas = getCurrentCanvasFn();
      if (canvas) {
        const activeObjects = canvas?.getActiveObjects?.();

        let isUpdate = false;
        if (Array.isArray(activeObjects) && activeObjects.length) {
          activeObjects.forEach((item) => {
            // @ts-ignore
            const id = String(item?.id || "");
            if ([OBJECT_ID.TEXT].includes(id)) {
              Object.entries(textConfig).forEach(([key, value]) => {
                if (key && value) {
                  // @ts-ignore
                  item?.set?.("styles", {});
                  // @ts-ignore
                  item?.set?.(key, value);
                  item?.setCoords?.();
                  isUpdate = true;
                }
              });
            }
          });

          if (isUpdate) {
            canvas?.requestRenderAll?.();
          }
        }
      }
    }
  }, [textConfig]);

  useEffect(() => {
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    const validateParamsFn = (e: Record<string, any>) => {
      const { id } = e?.target || {};
      const ids = [OBJECT_ID.CROP];
      if (ids.includes(id)) return false;

      return true;
    };

    const onObjectAddedChange = (e: Record<string, any>) => {
      const isSuccess = validateParamsFn(e);
      if (!isSuccess) return;

      syncCanvasIdRef.current = activeImage?.id;
      listenCanvasEventFn({
        isBatch: Boolean(e?.target?.isBatch),
      });

      if (isDrawingModeRef.current) {
        setTempDrawObjects((prev) => [...prev, e?.target].filter(Boolean));
      }
    };

    const onObjectModifiedChange = (e: Record<string, any>) => {
      const isSuccess = validateParamsFn(e);
      if (!isSuccess) return;

      syncCanvasIdRef.current = activeImage?.id;
      listenCanvasEventFn({
        isBatch: Boolean(e?.target?.isBatch),
      });
    };

    const onObjectRemovedChange = (e: Record<string, any>) => {
      syncCanvasIdRef.current = activeImage?.id;
      listenCanvasEventFn({
        isBatch: Boolean(e?.target?.isBatch),
      });
    };

    const onObjectRotatingChange = (info: Record<string, any>) => {
      const { target, e } = info || {};

      if (!target || !e) return;

      const angle = target?.angle || 0;
      const angleValue = (((angle % 360) + 540) % 360) - 180;
      onToggleModalClick("isAngle", true, {
        position: {
          left: e?.pageX + 16,
          top: e?.pageY,
        },
        angleText: `${Math.round(angleValue)}°`,
      });
    };

    canvas?.on?.("object:added", onObjectAddedChange);
    canvas?.on?.("object:modified", onObjectModifiedChange);
    canvas?.on?.("object:removed", onObjectRemovedChange);
    canvas?.on?.("object:rotating", onObjectRotatingChange);

    const onSelectionChange = () => {
      const activeObjects = canvas?.getActiveObjects?.();
      const objects = Array.isArray(activeObjects) ? activeObjects : [];
      // @ts-ignore
      const isTextTool = objects.every((item) => [OBJECT_ID.TEXT].includes(item?.id || ""));
      onToggleModalClick("isTextTool", isTextTool);

      const activeObject = canvas?.getActiveObject?.();
      // @ts-ignore
      if ([OBJECT_ID.TEXT].includes(activeObject?.id || "")) {
        setTextConfig((prevState) => {
          return {
            ...prevState,
            // @ts-ignore
            fontSize: activeObject?.fontSize || prevState?.fontSize,
            // @ts-ignore
            fontFamily: activeObject?.fontFamily || prevState?.fontFamily,
            fill: activeObject?.fill || prevState?.fill,
          };
        });
      }
    };

    canvas?.on?.("selection:created", onSelectionChange);
    canvas?.on?.("selection:updated", onSelectionChange);
    canvas?.on?.("selection:cleared", () => {
      onToggleModalClick("isTextTool", false);
    });

    const onMouseWheelChange = (opt: IEvent<WheelEvent>) => {
      const delta = opt?.e?.deltaY || 0;
      let zoom = canvas?.getZoom?.() || 1;
      zoom *= 0.999 ** delta;
      if (zoom > 20) zoom = 20;
      if (zoom < 0.01) zoom = 0.01;
      const x = opt?.e?.offsetX || 0;
      const y = opt?.e?.offsetY || 0;
      canvas.zoomToPoint(
        {
          x,
          y,
        },
        zoom
      );

      opt.e.preventDefault();
      opt.e.stopPropagation();

      const zoomPercentage = Math.round(zoom * 100);
      onCanvasInfoChange(activeImage?.id, {
        zoomPercentage,
      });
    };
    canvas?.on?.("mouse:wheel", onMouseWheelChange);

    const onKeyDownChange = (e: Record<string, any>) => {
      const tagName = e?.target?.tagName;
      if (["INPUT", "TEXTAREA"].includes(tagName)) return;

      if ([32].includes(e?.keyCode) && !isEditingText) {
        isSpacePressedRef.current = true;
        canvas.defaultCursor = "grab";

        canvas.forEachObject((obj) => {
          obj.evented = false;
        });

        canvas.requestRenderAll();
        e.preventDefault();
      }

      if ([8, 46].includes(e?.keyCode)) {
        const activeObjects = canvas?.getActiveObjects?.();
        if (Array.isArray(activeObjects) && activeObjects.length) {
          const objects = activeObjects.filter(
            (item: Record<string, any>) =>
              ![OBJECT_ID.BG, OBJECT_ID.SKETCH, OBJECT_ID.CROP].includes(
                item?.id
              )
          );
          if (Array.isArray(objects) && objects.length) {
            e.preventDefault();
            objects.forEach((item) => canvas.remove(item));
            discardActiveObjectFn(canvas);
          }
        }
      }
    };

    const onKeyUpChange = (e: Record<string, any>) => {
      const tagName = e?.target?.tagName;
      if (["INPUT", "TEXTAREA"].includes(tagName)) return;

      if ([32].includes(e?.keyCode)) {
        isSpacePressedRef.current = false;

        canvas.forEachObject((obj) => {
          obj.evented = true;
        });

        if (!isDraggingRef?.current) {
          canvas.defaultCursor = "default";
        }

        canvas.requestRenderAll();
      }
    };

    const onCanvasMouseDownChange = (opt: Record<string, any>) => {
      if (
        isSpacePressedRef?.current &&
        opt?.e?.button === 0 &&
        !isEditingText
      ) {
        isDraggingRef.current = true;
        canvas.defaultCursor = "grabbing";
        lastPosRef.current = {
          x: opt?.e?.clientX || 0,
          y: opt?.e?.clientY || 0,
        };
        canvas.selection = false;
      }
    };

    const onCanvasMouseMoveChange = (opt: Record<string, any>) => {
      if (isDraggingRef?.current) {
        const clientX = opt?.e?.clientX || 0;
        const clientY = opt?.e?.clientY || 0;
        const x = lastPosRef?.current?.x || 0;
        const y = lastPosRef?.current?.y || 0;
        const deltaX = clientX - x;
        const deltaY = clientY - y;

        const vpt = canvas?.viewportTransform || [];
        vpt[4] += deltaX;
        vpt[5] += deltaY;
        canvas.setViewportTransform(vpt);
        canvas.requestRenderAll();

        lastPosRef.current = {
          x: opt.e.clientX,
          y: opt.e.clientY,
        };
      }
    };

    const onCanvasMouseUpChange = () => {
      onToggleModalClick("isAngle", false);

      if (isDraggingRef?.current) {
        isDraggingRef.current = false;
        canvas.defaultCursor = isSpacePressedRef.current ? "grab" : "default";
        canvas.selection = false;
        canvas.requestRenderAll();
      }
    };

    window.addEventListener("keydown", onKeyDownChange);
    window.addEventListener("keyup", onKeyUpChange);
    canvas?.on?.("mouse:down", onCanvasMouseDownChange);
    canvas?.on?.("mouse:move", onCanvasMouseMoveChange);
    canvas?.on?.("mouse:up", onCanvasMouseUpChange);

    let isEditingText = false;
    canvas?.on?.("text:editing:entered", () => {
      isEditingText = true;
      canvas.selection = false;
    });
    canvas?.on?.("text:editing:exited", () => {
      isEditingText = false;
      canvas.selection = true;
    });

    return () => {
      canvas?.off?.("object:added", onObjectAddedChange);
      canvas?.off?.("object:modified", onObjectModifiedChange);
      canvas?.off?.("object:removed", onObjectRemovedChange);
      canvas?.off?.("object:rotating", onObjectRotatingChange);

      canvas?.off?.("selection:created", onSelectionChange);
      canvas?.off?.("selection:updated", onSelectionChange);
      canvas?.off?.("selection:cleared");

      canvas?.off?.("mouse:wheel");

      window.removeEventListener("keydown", onKeyDownChange);
      window.removeEventListener("keyup", onKeyUpChange);
      canvas?.off?.("mouse:down", onCanvasMouseDownChange);
      canvas?.off?.("mouse:move", onCanvasMouseMoveChange);
      canvas?.off?.("mouse:up", onCanvasMouseUpChange);

      canvas?.off?.("text:editing:entered");
      canvas?.off?.("text:editing:exited");
    };
  }, [activeImage?.id, canvasMap]);

  const getPictureInfoFn = async () => {
    const result = await Promise.resolve(
      Array.from({ length: 3, }, (_v, k) => k+1).map(index => {
        return {
          id: index,
          fileId: `${ index }`,
          description: `图片描述${ index }`,
          name: `图片${index}`,
        };
      })
    );
    if (!Array.isArray(result) || !result?.length) return;

    const imgDesMap_new = {};
    const historyMap_new = {};
    const imageList_new: Array<Record<string, any>> = [];
    const fileId_list: Array<string> = [];
    result.forEach((item) => {
      const id = item?.id;
      if (id) {
        Object.assign(historyMap_new, {
          [id]: {
            undoStacks: [],
            redoStacks: [],
          },
        });

        const canvas_ref = cloneDeep(canvasRef);
        imageList_new.push({
          ...item,
          canvasRef: canvas_ref,
        });

        fileId_list.push(item?.fileId);

        Object.assign(imgDesMap_new, {
          [id]: item?.description || "",
        });
      }
    });

    const imgs = await Promise.all(
      fileId_list.map((item) => sysFileDownloadApi(item))
    );
    if (Array.isArray(imgs) && imgs.length) {
      const cacheImgRef_new = {};
      fileId_list.forEach((item, index) => {
        Object.assign(cacheImgRef_new, {
          [item]: imgs?.[index] || "",
        });
      });

      cacheImgRef.current = cacheImgRef_new;
    }

    historyMap.current = historyMap_new;
    setImageList(imageList_new);

    const activeImage_new = imageList_new?.[0] || {};
    setActiveImage(activeImage_new);

    setImgDesMap(imgDesMap_new);
  };

  const initCanvasFn = (params: {
    imgInfo: Record<string, any>;
    ref?: HTMLCanvasElement | string | null;
    canvasRef?: fabric.Canvas;
  }) => {
    return new Promise((resolve) => {
      const imgInfo = params?.imgInfo;
      const ref = params?.ref;
      const canvasRef = params?.canvasRef;
      if (!imgInfo?.fileId) return resolve(null);

      const len = imageList?.length || 0;

      let width = window.innerWidth - 30 * 2 - 20;
      width = len > 1 ? width - 220 : width;
      const height = window?.innerHeight - 60 - 10 - 164 - 10;

      let canvas = canvasRef;
      if (!canvas && ref) {
        canvas = new fabric.Canvas(ref, {
          width,
          height,
          selection: true,
          preserveObjectStacking: true,
          backgroundColor: "#f0f2f5",
          controlsAboveOverlay: true,
        });
      }

      if (!canvas) return resolve(null);

      const imgUrl = cacheImgRef?.current?.[imgInfo?.fileId] || "";
      fabric.Image.fromURL(imgUrl, (img) => {
        if (img) {
          const imgWidth = img?.width || 0;
          const imgHeight = img?.height || 0;
          const sketchBg = new Image();
          sketchBg.onload = () => {
            const canvasW = canvas.getWidth();
            const canvasH = canvas.getHeight();

            const scale = calcImgScaleFn({
              canvasWidth: canvasW,
              canvasHeight: canvasH,
              imgWidth,
              imgHeight,
            });

            img.scale(scale);

            img.set({
              selectable: true,
              evented: true,
              // @ts-ignore
              erasable: false,
              id: OBJECT_ID.BG,
              left: (canvasW - img.getScaledWidth()) / 2,
              top: (canvasH - img.getScaledHeight()) / 2,
            });

            const sketch = new fabric.Rect({
              fill: "#fff",
              width: img.getScaledWidth(),
              height: img.getScaledHeight(),
              selectable: false,
              hasControls: false,
              hoverCursor: "default",
              // @ts-ignore
              id: OBJECT_ID.SKETCH,
              // @ts-ignore
              erasable: false,
            });
            sketch.set({
              left: (canvasW - sketch.getScaledWidth()) / 2,
              top: (canvasH - sketch.getScaledHeight()) / 2,
            });

            canvas.add(sketch, img);
            canvas.sendToBack(sketch);
            canvas.requestRenderAll();
            sketch.clone((cloned: any) => {
              canvas.clipPath = cloned;
              canvas.requestRenderAll();
            });

            const d = imgInfo?.id;
            const zoom = canvas?.getZoom?.() || 1;
            const zoomPercentage = Math.round(zoom * 100);
            canvasInfoRef.current = {
              ...canvasInfoRef.current,
              [d]: {
                zoomPercentage,
                originalImageWidth: sketch?.width,
                originalImageHeight: sketch?.height,
                sketch,
                imgScale: scale,
              },
            };

            listenCanvasEventFn({
              canvas,
              id: d,
              isSync: false,
            });

            resolve(canvas);
          };
          sketchBg.src = sketch_bg_png;
        }
      });
    });
  };

  const onToggleActiveClick = (key: IActiveType, value: boolean) => {
    if (!key) return;

    const bol = Boolean(value);
    setIsActiveMap((prev) => {
      return {
        ...(prev || {}),
        [key]: bol,
      };
    });
  };

  const onToggleModalClick = (
    key: IModalType,
    value: boolean,
    info?: Record<string, any>
  ) => {
    if (!key) return;

    setModalInfo(info || {});

    const bol = Boolean(value);
    if (["isPaintingTool"].includes(key)) {
      onToggleActiveClick("isDrawActive", bol);
    }
    setIsModalOpenMap((prev) => {
      return {
        ...(prev || {}),
        [key]: bol,
      };
    });
  };

  const handleModalFn = (key: IModalType, callBack?: () => void) => {
    if (!key) return;

    if (!["isPaintingTool", "isEraser"].includes(key)) {
      onResetPaintingClick();
    } else {
      setIsModalOpenMap({});
    }

    onCropCancelClick();

    setTimeout(() => {
      callBack?.();
    }, 0);
  };

  const initObjectPrototype = () => {
    fabric.Object.prototype.set({
      cornerColor: "#91C1FF",
      cornerStrokeColor: "#91C1FF",
      cornerSize: 14,
      cornerStyle: "circle",
      transparentCorners: false,
      borderColor: "#91C1FF",
    });
  };

  const renderRotateController = () => {
    const config = {
      render: renderSvgIcon(ROTATE_IMG),
      withConnection: false,
      x: 0,
      offsetY: -30,
    };

    Object.entries(config).forEach(([key, value]) => {
      // @ts-ignore
      fabric.Object.prototype.controls.mtr[key] = value;
    });
  };

  const getCurrentCanvasFn = (id?: number) => {
    const d = String(id || activeImage?.id);
    const canvas = canvasMap?.[d];
    return canvas;
  };

  const onResetPaintingClick = () => {
    onToggleModalClick("isEraser", false);
    useDrawingFn(false);
    setTempDrawObjects([]);
    setTimeout(() => {
      onToggleModalClick("isPaintingTool", false);
    }, 0);
  };

  const renderPaintingTool = () => {
    return (
      <Flex align="center" className="image_editor_drawing_tool">
        <MyTooltip
          placement="bottomLeft"
          trigger="click"
          color="#fff"
          title={
            <div className="image_editor_eraser__body">
              <span>橡皮擦粗细</span>
              <Slider
                min={1}
                max={100}
                value={eraserWidth}
                onChange={(value) => {
                  setEraserWidth(value);
                }}
              />
            </div>
          }
          classNames={{
            root: "image_editor_eraser",
          }}
          open={isOpenMap?.isEraser || false}
        >
          <div className="image_editor_drawing_tool__icon01">
            <MyTooltip title="橡皮擦" placement="bottom">
              <ToolButton
                color={isOpenMap?.isEraser ? "default" : undefined}
                variant={isOpenMap?.isEraser ? "filled" : undefined}
                onClick={() => {
                  onToggleModalClick("isEraser", !isOpenMap?.isEraser);
                }}
              >
                <img src={edit_icon01_png} />
              </ToolButton>
            </MyTooltip>
          </div>
        </MyTooltip>

        <div className="image_editor_drawing_tool__line" />

        <Flex align="center" className="image_editor_drawing_tool__config">
          <div className="image_editor_drawing_tool__config--left">
            <span>画笔粗细</span>
            <Slider
              min={1}
              max={100}
              value={strokeWidth}
              onChange={(value) => {
                setStrokeWidth(value);
              }}
            />
          </div>

          <div className="image_editor_drawing_tool__config--right">
            <span>画笔颜色</span>
            <ColorPicker
              size="small"
              value={strokeColor}
              onChange={(_value, css) => {
                setStrokeColor(css);
              }}
            />
          </div>
        </Flex>

        <div className="image_editor_drawing_tool__line" />

        <div className="image_editor_drawing_tool__btn">
          <ToolButton
            onClick={() => {
              if (Array.isArray(tempDrawObjects) && tempDrawObjects.length) {
                isSyncCanvas.current = true;
              }

              onResetPaintingClick();
            }}
          >
            <img
              src={edit_icon02_png}
              className="image_editor_drawing_tool__icon02"
            />
          </ToolButton>

          <ToolButton
            onClick={() => {
              onResetPaintingClick();

              const canvas = getCurrentCanvasFn();
              tempDrawObjects?.forEach?.((item) => {
                canvas?.remove?.(item);
              });
              canvas.requestRenderAll();
            }}
          >
            <img
              src={edit_icon03_png}
              className="image_editor_drawing_tool__icon03"
            />
          </ToolButton>
        </div>
      </Flex>
    );
  };

  const renderTextTool = () => {
    return (
      <Flex align="center" justify="center" className="ie_text_tool_box__body">
        <MyTooltip title="字体" placement="bottom">
          <Select
            style={{ width: 166 }}
            value={textConfig?.fontFamily}
            options={FONT_FAMILY_OPTIONS}
            onChange={(value) => {
              setTextConfig((prevState) => {
                return {
                  ...prevState,
                  fontFamily: value,
                };
              });
            }}
          />
        </MyTooltip>

        <div className="ie_text_tool_box__body--line" />

        <MyTooltip title="字号" placement="bottom">
          <Select
            style={{ width: 100 }}
            options={FONT_SIZE_OPTIONS}
            value={textConfig?.fontSize}
            onChange={(value) => {
              setTextConfig((prevState) => {
                return {
                  ...prevState,
                  fontSize: value,
                };
              });
            }}
          />
        </MyTooltip>

        <div className="ie_text_tool_box__body--line" />

        <ColorPicker
          size="small"
          value={textConfig?.fill}
          onChange={(_value, css) => {
            setTextConfig((prevState) => {
              return {
                ...prevState,
                fill: css,
              };
            });
          }}
        >
          <ToolButton>
            <div className="ie_text_tool_box__body--font">
              <img src={font_png} />
              <div
                style={{
                  background: textConfig?.fill,
                }}
              ></div>
            </div>
          </ToolButton>
        </ColorPicker>
      </Flex>
    );
  };

  const undo = () => {
    const id = activeImage?.id;
    const historyMap_new = cloneDeep(historyMap.current);
    const historyMapItem = historyMap_new?.[id];
    const canvas = getCurrentCanvasFn();
    const undoStacks = historyMapItem?.undoStacks;
    const redoStacks = historyMapItem?.redoStacks;
    if (isDoing.current) return;
    if (!Array.isArray(undoStacks) || !undoStacks.length) return;

    const content = undoStacks.pop();
    if (content) {
      redoStacks?.push?.(content);
    }

    const len = undoStacks?.length ?? 0;
    const item = undoStacks?.[len - 1];
    if (!item || !canvas) return;

    isDoing.current = true;
    historyMap.current = historyMap_new;
    canvas?.loadFromJSON?.(item, () => {
      discardActiveObjectFn(canvas);
      isDoing.current = false;
      isSyncCanvas.current = true;

      const objects = item?.objects || [];
      if (Array.isArray(objects) && objects.length) {
        const bol = objects.some((item) => item?.isBatch);
        if (bol) {
          setIsSpinning(true);
          syncAllCanvasesFn().finally(() => {
            setIsSpinning(false);
          });
        }
      }
    });
  };

  const redo = () => {
    const id = activeImage?.id;
    const historyMap_new = cloneDeep(historyMap.current);
    const historyMapItem = historyMap_new?.[id];
    const canvas = getCurrentCanvasFn();
    const undoStacks = historyMapItem?.undoStacks;
    const redoStacks = historyMapItem?.redoStacks;
    if (isDoing.current) return;
    if (!Array.isArray(redoStacks) || !redoStacks.length) return;

    const content = redoStacks.pop();
    if (!content || !canvas) return;

    undoStacks.push(content);

    isDoing.current = true;
    historyMap.current = historyMap_new;
    canvas?.loadFromJSON?.(content, () => {
      discardActiveObjectFn(canvas);
      isDoing.current = false;
      isSyncCanvas.current = true;

      const objects = content?.objects || [];
      if (Array.isArray(objects) && objects.length) {
        const bol = objects.some((item) => item?.isBatch);
        if (bol) {
          setIsSpinning(true);
          syncAllCanvasesFn().finally(() => {
            setIsSpinning(false);
          });
        }
      }
    });
  };

  const resetCanvasFn = async (
    canvas?: fabric.Canvas,
    id?: string | number
  ) => {
    canvas = canvas || getCurrentCanvasFn();
    if (!canvas) return;

    id = id || activeImage?.id;
    if (!id) return;

    const historyMap_new = cloneDeep(historyMap.current);
    const historyMapItem = historyMap_new?.[id];
    const undoStacks = historyMapItem?.undoStacks;
    if (!Array.isArray(undoStacks) || !undoStacks.length) return;
    const content = undoStacks?.[0];
    if (!content) return;

    const last_undoStack = undoStacks?.[undoStacks?.length - 1];
    if (last_undoStack && isEqual(content, last_undoStack)) return;

    canvas?.loadFromJSON?.(content, () => {
      discardActiveObjectFn(canvas);

      historyMap.current = {
        ...historyMap?.current,
        [id]: {
          undoStacks: [content],
          redoStacks: [],
        },
      };
    });
  };

  const batchResetCanvasFn = async (isBatchReset: boolean) => {
    isBatchReset = isBatchReset || false;

    setIsSpinning(true);

    isReset.current = true;

    const promise_list: any[] = [];
    if (!isBatchReset) {
      batchTextRecordRef.current = {
        ...batchTextRecordRef?.current,
        [activeImage?.id]: [],
      };

      promise_list.push(resetCanvasFn());
    } else {
      batchTextRecordRef.current = {};

      Object.entries(canvasMap).forEach(([key, canvas]) => {
        const currentId = Number(key);
        if (currentId && canvas) {
          promise_list.push(resetCanvasFn(canvas, currentId));
        }
      });
    }

    Promise.all(promise_list).finally(() => {
      setTimeout(() => {
        if (isBatchReset) {
          isSyncCanvas.current = false;
        }
        isReset.current = false;
        setIsSpinning(false);
      }, 1000);
    });
  };

  const rotateCanvasFn = (degrees: number) => {
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    const ojects = canvas?.getObjects?.();

    if (!Array.isArray(ojects) || !ojects.length) return;

    const center = {
      x: (canvas?.width || 0) / 2,
      y: (canvas?.height || 0) / 2,
    };

    ojects.forEach((item) => {
      const vector = {
        x: (item?.left || 0) - center.x,
        y: (item?.top || 0) - center.y,
      };

      const angleRad = (degrees * Math.PI) / 180;
      const cos = Math.cos(angleRad);
      const sin = Math.sin(angleRad);

      const rotatedVector = {
        x: vector.x * cos - vector.y * sin,
        y: vector.x * sin + vector.y * cos,
      };

      item.set({
        left: center.x + rotatedVector.x,
        top: center.y + rotatedVector.y,
        angle: ((item?.angle || 0) + degrees) % 360,
      });

      item?.setCoords?.();
    });

    canvas?.requestRenderAll?.();
    isSyncCanvas.current = true;

    listenCanvasEventFn();
  };

  const useDrawingFn = (value: boolean) => {
    const isDrawingMode = Boolean(value);
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    canvas.forEachObject((obj) => {
      obj.evented = !isDrawingMode;
    });

    canvas.isDrawingMode = isDrawingMode;
    canvas.freeDrawingBrush.width = strokeWidth;
    canvas.freeDrawingBrush.color = strokeColor;

    isDrawingModeRef.current = isDrawingMode;
  };

  const listenCanvasEventFn = (params?: {
    canvas?: fabric.Canvas;
    id?: string | number;
    isSync?: boolean;
    isBatch?: boolean;
  }) => {
    const canvas = params?.canvas || getCurrentCanvasFn();
    const id = params?.id || activeImage?.id;
    const isSync = params?.isSync ?? true;
    const isBatch = params?.isBatch ?? false;

    if (!id || !canvas) return;
    if (isDoing?.current || isSaving?.current || isReset?.current) return;

    isSaving.current = true;

    const historyMap_new = cloneDeep(historyMap?.current);
    const historyMapItem = historyMap_new?.[id];
    if (!historyMapItem) return;

    const { undoStacks } = historyMapItem;
    const json = canvas?.toJSON?.();

    if (!json) return;

    isSaving.current = false;

    const last_undoStack = undoStacks?.[undoStacks?.length - 1];
    if (last_undoStack && isEqual(json, last_undoStack)) return;

    undoStacks.push(json);
    isSyncCanvas.current = isSync;
    historyMap.current = historyMap_new;

    if (isBatch) {
      setIsSpinning(true);
      syncAllCanvasesFn().finally(() => {
        setIsSpinning(false);
      });
    }
  };

  const addTextFn = (params?: Record<string, any>) => {
    const isBatch = Boolean(params?.isBatch);

    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    const objects = canvas?.getObjects?.();
    if (!Array.isArray(objects) || !objects.length) return;

    const sketchRectObject = objects.find(
      (item: Record<string, any>) => item?.id === OBJECT_ID.SKETCH
    ) as fabric.Rect;
    if (!sketchRectObject) return;

    const textId = `${Date.now()}`;
    if (isBatch) {
      imageList.forEach((item) => {
        const id = item?.id;
        if (id) {
          const record = batchTextRecordRef?.current?.[id] || [];
          record.push(textId);
          Object.assign(batchTextRecordRef?.current, {
            [id]: record,
          });
        }
      });
    }

    const imgLeft = sketchRectObject?.center?.()?.left || 0;
    const imgTop = sketchRectObject?.center?.()?.top || 0;

    const text = new fabric.Textbox("请输入文本", {
      fill: DEFAULT_TEXT_CONFIG?.fill,
      fontFamily: DEFAULT_TEXT_CONFIG?.fontFamily,
      fontSize: DEFAULT_TEXT_CONFIG?.fontSize,
      left: imgLeft,
      top: imgTop,
      width: 210,
      // @ts-ignore
      id: "text",
      splitByGrapheme: true,
      // @ts-ignore
      erasable: false,
      isBatch,
      textId,
    });
    canvas?.add?.(text);
    canvas.setActiveObject(text);
  };

  const zoomInFn = () => {
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    let zoom = canvas?.getZoom?.() || 1;
    zoom *= 1.1;
    if (zoom > 20) zoom = 20;

    const width = canvas?.width || 0;
    const height = canvas?.height || 0;
    const x = width / 2;
    const y = height / 2;
    canvas.zoomToPoint(
      {
        x,
        y,
      },
      zoom
    );

    const zoomPercentage = Math.round(zoom * 100);
    onCanvasInfoChange(activeImage?.id, {
      zoomPercentage,
    });
    isSyncCanvas.current = true;
  };

  const zoomOutFn = () => {
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    let zoom = canvas?.getZoom?.() || 1;
    zoom *= 0.9;
    if (zoom < 0.01) zoom = 0.01;

    const width = canvas?.width || 0;
    const height = canvas?.height || 0;
    const x = width / 2;
    const y = height / 2;
    canvas.zoomToPoint(
      {
        x,
        y,
      },
      zoom
    );

    const zoomPercentage = Math.round(zoom * 100);
    onCanvasInfoChange(activeImage?.id, {
      zoomPercentage,
    });
    isSyncCanvas.current = true;
  };

  const onZoomChange = (value: number) => {
    value = Number(value) || 1;
    if (!Array.isArray(imageList) || !imageList.length) return;

    if (getCurrentCanvasInfoFn()?.zoomPercentage === value) return;

    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    const zoom = Number((value / 100)?.toFixed?.(2));
    const width = canvas?.width || 0;
    const height = canvas?.height || 0;
    const x = width / 2;
    const y = height / 2;
    canvas.zoomToPoint(
      {
        x,
        y,
      },
      zoom
    );

    const zoomPercentage = value;
    onCanvasInfoChange(activeImage?.id, {
      zoomPercentage,
    });
    isSyncCanvas.current = true;
  };

  const onSketchSizeChange = (data: string, type: "w" | "h") => {
    if (!type) return;

    const value = String(data || "");
    let val = Number(value) || 10;
    val = Math.round(val);
    if (val < 10) val = 10;
    if (val > 2048) val = 2048;

    const params: Record<string, any> = { ...activeImage };
    switch (type) {
      case "w":
        if (params?.originalImageWidth === val) return;

        Object.assign(params, {
          originalImageWidth: val,
        });

        if (isUseRatio) {
          Object.assign(params, {
            originalImageHeight: val,
          });
        }
        break;
      case "h":
        if (params?.originalImageHeight === val) return;

        Object.assign(params, {
          originalImageHeight: val,
        });

        if (isUseRatio) {
          Object.assign(params, {
            originalImageWidth: val,
          });
        }
        break;
    }

    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    updateSketchSizeFn(canvas, { ...params });
  };

  const onCropSizeChange = (data: string, type: "w" | "h") => {
    if (!type) return;

    const value = String(data || "");
    let val = Number(value) || 10;
    val = Math.round(val);
    if (val < 10) val = 10;

    const params: Record<string, any> = {};
    switch (cropRatio) {
      case "1:1":
        Object.assign(params, {
          cropWidth: val,
          cropHeight: val,
        });
        break;
      case "3:4":
        switch (type) {
          case "w":
            Object.assign(params, {
              cropWidth: val,
              cropHeight: (4 * val) / 3,
            });
            break;
          case "h":
            Object.assign(params, {
              cropWidth: (3 * val) / 4,
              cropHeight: val,
            });
            break;
        }
        break;
      case "4:3":
        switch (type) {
          case "w":
            Object.assign(params, {
              cropWidth: val,
              cropHeight: (3 * val) / 4,
            });
            break;
          case "h":
            Object.assign(params, {
              cropWidth: (4 * val) / 3,
              cropHeight: val,
            });
            break;
        }
        break;
      case "9:16":
        switch (type) {
          case "w":
            Object.assign(params, {
              cropWidth: val,
              cropHeight: (16 * val) / 9,
            });
            break;
          case "h":
            Object.assign(params, {
              cropWidth: (9 * val) / 16,
              cropHeight: val,
            });
            break;
        }
        break;
      case "16:9":
        switch (type) {
          case "w":
            Object.assign(params, {
              cropWidth: val,
              cropHeight: (9 * val) / 16,
            });
            break;
          case "h":
            Object.assign(params, {
              cropWidth: (16 * val) / 9,
              cropHeight: val,
            });
            break;
        }
        break;
      default:
        switch (type) {
          case "w":
            Object.assign(params, {
              cropWidth: val,
              cropHeight: activeImage?.cropInfo?.cropHeight || 0,
            });
            break;
          case "h":
            Object.assign(params, {
              cropWidth: activeImage?.cropInfo?.cropWidth || 0,
              cropHeight: val,
            });
            break;
        }
        break;
    }

    if (!params || !Object.keys(params).length) return;

    activeImage?.cropInfo?.changeCropRect?.({
      width: params?.cropWidth,
      height: params?.cropHeight,
    });
  };

  const onUpdateMaskChange = (info: Record<string, any>) => {
    if (!info) return;

    getCropInfoFn((params) => {
      if (!params || !Object.keys(params).length) return;

      const cropRectWidth = params?.cropRectObject?.width || 0;
      const cropRectHeight = params?.cropRectObject?.height || 0;
      const cropRectScaleX = params?.cropRectObject?.scaleX || 1;
      const cropRectScaleY = params?.cropRectObject?.scaleY || 1;

      params?.cropRectObject?.setCoords?.();
      params?.canvas?.requestRenderAll?.();

      const cropWidth = cropRectWidth * cropRectScaleX;
      const cropHeight = cropRectHeight * cropRectScaleY;
      setActiveImage({
        ...activeImage,
        cropInfo: {
          ...info,
          cropWidth: cropWidth > 10 ? cropWidth : 10,
          cropHeight: cropHeight > 10 ? cropHeight : 10,
        },
      });
    });
  };

  const onCroppingClick = (ratio: string) => {
    const canvas = getCurrentCanvasFn();
    if (!canvas || !ratio) return;

    const canvasWidth = canvas?.getWidth?.() || 0;
    const canvasHeight = canvas?.getHeight?.() || 0;
    if (!canvasWidth || !canvasHeight) return;

    const sketch = getCurrentCanvasInfoFn()?.sketch as fabric.Rect;
    if (!sketch) return;

    const sketchWidth = sketch?.getScaledWidth?.() || 0;
    const sketchHeight = sketch?.getScaledHeight?.() || 0;
    if (!sketchWidth || !sketchHeight) return;

    let cropSizeInfo: Partial<{
      width: number;
      height: number;
    }> = {};
    switch (ratio) {
      case "0":
        break;
      case "1:1":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 1 / 1,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "2:3":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 2 / 3,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "3:2":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 3 / 2,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "3:4":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 3 / 4,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "4:3":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 4 / 3,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "9:16":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 9 / 16,
          sketchWidth,
          sketchHeight,
        });
        break;
      case "16:9":
        cropSizeInfo = _getPresetPropertiesForCropSize({
          presetRatio: 16 / 9,
          sketchWidth,
          sketchHeight,
        });
        break;
    }

    const cropWidth = cropSizeInfo?.width || sketchWidth;
    const cropHeight = cropSizeInfo?.height || sketchHeight;

    const cropRect = new fabric.Rect({
      width: cropWidth,
      height: cropHeight,
      fill: "rgba(0, 0, 0, 0.5)",
      selectable: true,
      // @ts-ignore
      id: OBJECT_ID.CROP,
    });

    const controlsVisibilityConfig = {
      mtr: false,
    };
    if (!["0"].includes(ratio)) {
      Object.assign(controlsVisibilityConfig, {
        mb: false,
        ml: false,
        mr: false,
        mt: false,
      });
    }

    cropRect.setControlsVisibility({ ...controlsVisibilityConfig });

    const cropRectWidth = cropRect?.getScaledWidth?.() || 0;
    const cropRectHeight = cropRect?.getScaledHeight?.() || 0;
    cropRect.set({
      left: (canvasWidth - cropRectWidth) / 2,
      top: (canvasHeight - cropRectHeight) / 2,
    });
    const cropRectScaleX = cropRect.scaleX || 1;
    const cropRectScaleY = cropRect.scaleY || 1;

    canvas.add(cropRect);
    canvas.setActiveObject(cropRect);
    sketch?.setCoords?.();
    cropRect.setCoords();
    canvas.requestRenderAll();

    const cropInfo = {
      ...activeImage?.cropInfo,
      cropRatio: ratio,
      cropIng: true,
      cropWidth: cropRectWidth,
      cropHeight: cropRectHeight,
      changeCropRect: (params: { width: number; height: number }) => {
        const w = Number(params?.width) || 0;
        const h = Number(params?.height) || 0;

        cropRect.set({
          width: w / cropRectScaleX,
          height: h / cropRectScaleY,
        });

        onUpdateMaskChange(cropInfo);
      },
    };

    cropRect.on("moving", () => {
      onUpdateMaskChange(cropInfo);
    });
    cropRect.on("scaling", () => {
      onUpdateMaskChange(cropInfo);
    });
    cropRect.on("rotating", () => {
      onUpdateMaskChange(cropInfo);
    });
    setActiveImage({
      ...activeImage,
      cropInfo,
    });
  };

  const getCropInfoFn = (
    callBack: (params: {
      canvasWidth: number;
      canvasHeight: number;
      cropRectWidth: number;
      cropRectHeight: number;
      cropRectLeft: number;
      cropRectTop: number;
      imgScaleX: number;
      imgSscaleY: number;
      imgLeft: number;
      imgTop: number;

      imgObject: fabric.Image;
      cropRectObject: fabric.Rect;
      canvas: fabric.Canvas;

      cropRectScaleX: number;
      cropRectScaleY: number;
    }) => void
  ) => {
    const canvas = getCurrentCanvasFn();
    if (!canvas) return;

    const canvasWidth = canvas?.getWidth() || 0;
    const canvasHeight = canvas?.getHeight() || 0;

    const objects = canvas?.getObjects?.();
    if (!Array.isArray(objects) || !objects.length) return;

    const imgObject = objects.find(
      (item: Record<string, any>) => item?.id === OBJECT_ID.BG
    ) as fabric.Image;
    const cropRectObject = objects.find(
      (item: Record<string, any>) => item?.id === OBJECT_ID.CROP
    ) as fabric.Rect;
    if (!imgObject || !cropRectObject) return;

    const cropRectWidth = cropRectObject?.width || 0;
    const cropRectHeight = cropRectObject?.height || 0;
    const cropRectLeft = cropRectObject?.left || 0;
    const cropRectTop = cropRectObject?.top || 0;
    const cropRectScaleX = cropRectObject.scaleX || 1;
    const cropRectScaleY = cropRectObject.scaleY || 1;

    const imgScaleX = imgObject?.scaleX || 1;
    const imgSscaleY = imgObject?.scaleY || 1;
    const imgLeft = imgObject.left || 0;
    const imgTop = imgObject.top || 0;

    const params = {
      canvasWidth,
      canvasHeight,
      cropRectWidth,
      cropRectHeight,
      cropRectLeft,
      cropRectTop,
      imgScaleX,
      imgSscaleY,
      imgLeft,
      imgTop,

      imgObject,
      cropRectObject,
      canvas,

      cropRectScaleX,
      cropRectScaleY,
    };

    callBack?.(params);
  };

  const onCropConfirmClick = () => {
    getCropInfoFn((params) => {
      if (!params || !Object.keys(params).length) return;

      const cropRectWidth = params?.cropRectObject?.width || 0;
      const cropRectHeight = params?.cropRectObject?.height || 0;
      const cropRectLeft = params?.cropRectObject?.left || 0;
      const cropRectTop = params?.cropRectObject?.top || 0;
      const cropRectScaleX = params?.cropRectObject?.scaleX || 1;
      const cropRectScaleY = params?.cropRectObject?.scaleY || 1;

      const scaleX = params?.imgObject?.scaleX || 1;
      const scaleY = params?.imgObject?.scaleY || 1;
      const left = params?.imgObject?.left || 0;
      const top = params?.imgObject?.top || 0;

      const img_w = params?.imgObject?.width || 0;
      const img_h = params?.imgObject?.height || 0;

      const cropX = (cropRectLeft - left) / scaleX;
      const cropY = (cropRectTop - top) / scaleY;

      const cropWidth = cropRectWidth * (cropRectScaleX / scaleX);
      const cropHeight = cropRectHeight * (cropRectScaleY / scaleY);

      const w_bol = cropWidth - img_w > 4;
      const width = w_bol ? img_w : cropWidth;

      const h_bol = cropHeight - img_h > 4;
      const height = h_bol ? img_h : cropHeight;

      const x1 = params?.imgObject?.cropX || 0;
      let x2 = cropX;
      if (w_bol) {
        x2 = x1;
      } else {
        x2 = cropX + x1;
      }

      const y1 = params?.imgObject?.cropY || 0;
      let y2 = cropY;
      if (h_bol) {
        y2 = y1;
      } else {
        y2 = cropY + y1;
      }

      params?.imgObject?.set?.({
        cropX: x2,
        cropY: y2,
        width,
        height,
      });

      params?.imgObject?.set?.({
        left: (params?.canvasWidth - params?.imgObject?.getScaledWidth?.()) / 2,
        top:
          (params?.canvasHeight - params?.imgObject?.getScaledHeight?.()) / 2,
      });

      params?.canvas?.remove?.(params?.cropRectObject);
      setActiveImage({
        ...activeImage,
        cropInfo: {},
      });

      isSyncCanvas.current = true;

      updateSketchSizeFn(params.canvas, {
        originalImageWidth: activeImage?.cropInfo?.cropWidth,
        originalImageHeight: activeImage?.cropInfo?.cropHeight,
      });
    });
  };

  const updateSketchSizeFn = (
    canvas: fabric.Canvas,
    params: Record<string, any>
  ) => {
    if (!canvas) return;

    const currentCanvasInfo = getCurrentCanvasInfoFn();
    const sketch = currentCanvasInfo?.sketch as fabric.Rect;
    if (!sketch) return;

    const w =
      params?.originalImageWidth || currentCanvasInfo?.originalImageWidth || 0;
    const h =
      params?.originalImageHeight ||
      currentCanvasInfo?.originalImageHeight ||
      0;
    const canvasW = canvas.getWidth();
    const canvasH = canvas.getHeight();
    if (w && h) {
      sketch?.set?.({
        width: w,
        height: h,
        left: (canvasW - w) / 2,
        top: (canvasH - h) / 2,
      });
      sketch?.setCoords?.();
      sketch.clone((cloned: any) => {
        canvas.clipPath = cloned;
        canvas.requestRenderAll();
      });

      const zoom = canvas?.getZoom?.() || 1;
      const zoomPercentage = Math.round(zoom * 100);
      onCanvasInfoChange(activeImage?.id, {
        ...params,
        zoomPercentage,
      });
      isSyncCanvas.current = true;
    }
  };

  const onCropCancelClick = () => {
    getCropInfoFn((params) => {
      if (!params || !Object.keys(params).length) return;

      params?.canvas?.remove?.(params?.cropRectObject);
      setActiveImage({
        ...activeImage,
        cropInfo: {},
      });
    });
  };

  const syncCanvasesFn = async (params: {
    prevId: number;
    currentId: number;
  }) => {
    const prevId = params?.prevId;
    const currentId = params?.currentId;
    if (!prevId || !currentId) return;
    if (prevId === currentId) return;

    const prevCanvas = getCurrentCanvasFn(prevId);
    const currentCanvas = getCurrentCanvasFn(currentId);
    if (!prevCanvas || !currentCanvas) return;

    const record_prev = batchTextRecordRef?.current?.[prevId];
    if (!Array.isArray(record_prev) || !record_prev.length) return;

    const record_current = batchTextRecordRef?.current?.[currentId];
    if (!Array.isArray(record_current) || !record_current.length) return;

    const prevObjectsData = prevCanvas
      .getObjects()
      .filter(
        (item: Record<string, any>) =>
          [OBJECT_ID.TEXT].includes(item?.id) &&
          item?.isBatch &&
          record_current.includes(item?.textId)
      )
      .map((item) => item?.toObject?.())
      .filter(Boolean);

    currentCanvas
      .getObjects()
      .filter(
        (item: Record<string, any>) =>
          ![OBJECT_ID.BG, OBJECT_ID.SKETCH].includes(item?.id)
      )
      .filter(
        (item: Record<string, any>) =>
          [OBJECT_ID.TEXT].includes(item?.id) &&
          item?.isBatch &&
          record_prev.includes(item?.textId)
      )
      .forEach((item) => currentCanvas.remove(item));

    if (!Array.isArray(prevObjectsData) || !prevObjectsData.length) return;

    fabric.util.enlivenObjects(
      prevObjectsData,
      (objectsData: Array<fabric.Object>) => {
        if (!Array.isArray(objectsData) || !objectsData.length) return;

        objectsData.forEach((item) => {
          if (item) {
            currentCanvas.add(item);
          }
        });

        currentCanvas.requestRenderAll();
        listenCanvasEventFn({
          canvas: currentCanvas,
          id: currentId,
        });
      },
      ""
    );

    return true;
  };

  const onCanvasInfoChange = (id: number, params: Record<string, any>) => {
    if (!id || !params) return;

    setCanvasInfo((prevState) => {
      const obj = prevState?.[id];
      const result = {
        ...obj,
        ...params,
      };

      return {
        ...prevState,
        [id]: result,
      };
    });
  };

  const getCurrentCanvasInfoFn = useCallback(
    (id?: number) => {
      id = id || activeImage?.id;

      const result = canvasInfo[id || ""] || {};
      return result;
    },
    [canvasInfo, activeImage?.id]
  );

  const imgDesValue = useMemo(() => {
    const content = String(imgDesMap[activeImage?.id] || "");
    return content;
  }, [imgDesMap, activeImage]);

  const imgDesNum = useMemo(() => {
    return imgDesValue?.length;
  }, [imgDesValue]);

  const onCopyClick = (text: string) => {
    try {
      navigator?.clipboard?.writeText?.(text)?.finally?.(() => {
        message.success("复制成功");
      });
    } catch (_error) {
      message.error("复制失败，请重试");
    }
  };

  const syncAllCanvasesFn = (): Promise<boolean> => {
    return new Promise((resolve) => {
      if (!isSyncCanvas?.current) return resolve(false);
      if (!canvasMap || !Object.keys(canvasMap).length) return resolve(false);

      const id = syncCanvasIdRef?.current;

      if (!id) return resolve(false);

      const promise_list: any[] = [];
      Object.entries(canvasMap).forEach(([key, item]) => {
        const currentId = Number(key);
        if (key && item) {
          if (currentId !== id) {
            promise_list.push(
              syncCanvasesFn({
                prevId: id,
                currentId,
              })
            );
          } else {
            promise_list.push(Promise.resolve(true));
          }
        }
      });
      Promise.all(promise_list).finally(() => {
        setTimeout(() => {
          isUpdateCanvas.current = isSyncCanvas.current;
          isSyncCanvas.current = false;
          resolve(true);
        }, 1000);
      });
    });
  };

  const onZoomScaleChange = (e: Record<string, any>) => {
    const canvas = getCurrentCanvasFn();
    const zoom = canvas?.getZoom?.();

    const value = String(e?.target?.value || "").replace("%", "");
    let val = Number(value) || zoom * 100;
    val = Math.round(val);
    if (val < 1) val = 1;
    if (val > 2000) val = 2000;

    onZoomChange(val);
  };

  const discardActiveObjectFn = (canvas?: fabric.Canvas) => {
    canvas = canvas || getCurrentCanvasFn();
    if (!canvas) return;

    canvas?.discardActiveObject?.();
    canvas?.requestRenderAll?.();
  };

  return (
    <Spin spinning={isSpinning}>
      <div className="image-editor-page-box">
        <Row>
          <div className="header-tool">
            <Flex
              style={{
                marginLeft: imageList?.length > 1 ? 220 : 96,
              }}
              align="center"
              className="ie_header_tool_size"
            >
              <Space size={16}>
                <Col className="zoom-tool">
                  <div className="zoom-box">
                    <MyTooltip title="放大">
                      <div>
                        <PlusOutlined onClick={() => zoomInFn()} />
                      </div>
                    </MyTooltip>
                    <MyTooltip title="自定义缩放比例">
                      <InputNumber
                        value={getCurrentCanvasInfoFn()?.zoomPercentage}
                        min={1}
                        max={2000}
                        formatter={(value) => `${value}%`}
                        parser={(value) =>
                          (value?.replace?.("%", "") || "") as any
                        }
                        controls={false}
                        precision={0}
                        onBlur={(e) => {
                          onZoomScaleChange(e);
                        }}
                        onPressEnter={(e) => {
                          onZoomScaleChange(e);
                        }}
                        className="zoom-box__input"
                      />
                    </MyTooltip>
                    <MyTooltip title="缩小">
                      <div>
                        <MinusOutlined onClick={() => zoomOutFn()} />
                      </div>
                    </MyTooltip>
                  </div>
                </Col>

                <Flex gap={6}>
                  <MyTooltip title="画板宽度">
                    <InputNumber
                      value={getCurrentCanvasInfoFn()?.originalImageWidth}
                      min={10}
                      max={2048}
                      precision={0}
                      prefix="W"
                      onBlur={(e) => {
                        onSketchSizeChange(e?.target?.value, "w");
                      }}
                      onPressEnter={(e: Record<string, any>) => {
                        onSketchSizeChange(e?.target?.value, "w");
                      }}
                      onFocus={() => {
                        onCropCancelClick();
                      }}
                      controls={false}
                    />
                  </MyTooltip>

                  <MyTooltip title={isUseRatio ? "解绑比例" : "约束比例"}>
                    <ToolButton
                      onClick={() => {
                        setIsUseRatio(!isUseRatio);
                      }}
                    >
                      <Icon
                        component={GuanlianIcon}
                        style={{
                          color: !isUseRatio ? "#676C75" : "#1664FF",
                          fontSize: 14,
                        }}
                      />
                    </ToolButton>
                  </MyTooltip>

                  <MyTooltip title="画板高度">
                    <InputNumber
                      value={getCurrentCanvasInfoFn()?.originalImageHeight}
                      min={10}
                      max={2048}
                      precision={0}
                      prefix="H"
                      onBlur={(e) => {
                        onSketchSizeChange(e?.target?.value, "h");
                      }}
                      onPressEnter={(e: Record<string, any>) => {
                        onSketchSizeChange(e?.target?.value, "h");
                      }}
                      onFocus={() => {
                        onCropCancelClick();
                      }}
                      controls={false}
                    />
                  </MyTooltip>
                </Flex>
              </Space>

              <div className="ie_header_tool_size__line"></div>

              <Space
                style={{
                  alignItems: "center",
                }}
              >
                <Dropdown
                  menu={{
                    items: [
                      { label: "自由裁剪", key: "0" },
                      { label: "1:1", key: "1:1" },
                      { label: "2:3", key: "2:3" },
                      { label: "3:2", key: "3:2" },
                      { label: "3:4", key: "3:4" },
                      { label: "4:3", key: "4:3" },
                      { label: "9:16", key: "9:16" },
                      { label: "16:9", key: "16:9" },
                    ],
                    onClick: ({ key }) => {
                      setCropRatio(key);
                      onCroppingClick(key);
                    },
                  }}
                  trigger={["click"]}
                  placement="bottom"
                  arrow={{ pointAtCenter: true }}
                  onOpenChange={(open) => {
                    onToggleActiveClick("isCropActive", open);

                    if (open) {
                      handleModalFn("-1", () => {
                        discardActiveObjectFn();
                      });
                    }
                  }}
                >
                  <div>
                    <MyTooltipButton
                      tooltipProps={{
                        title: "裁剪",
                      }}
                    >
                      <Icon
                        component={CaijianIcon}
                        style={{
                          color: !isActiveMap?.isCropActive
                            ? "#676C75"
                            : "#1664FF",
                          fontSize: 16,
                        }}
                      />
                    </MyTooltipButton>
                  </div>
                </Dropdown>
                <MyTooltip
                  title={renderPaintingTool()}
                  open={isOpenMap?.isPaintingTool || false}
                  classNames={{
                    root: "image_editor_drawing_tool_box",
                  }}
                >
                  <div>
                    <MyTooltipButton
                      tooltipProps={{
                        title: "画笔",
                      }}
                      buttonProps={{
                        onClick: () => {
                          if (isOpenMap?.isPaintingTool) return;

                          handleModalFn("isPaintingTool", () => {
                            onToggleModalClick("isPaintingTool", true);
                            useDrawingFn(true);
                          });
                        },
                      }}
                    >
                      <Icon
                        component={HuabiIcon}
                        style={{
                          color: !isActiveMap?.isDrawActive
                            ? "#676C75"
                            : "#1664FF",
                          fontSize: 16,
                        }}
                      />
                    </MyTooltipButton>
                  </div>
                </MyTooltip>

                <MyTooltip
                  title={renderTextTool()}
                  open={isOpenMap?.isTextTool || false}
                  classNames={{
                    root: "ie_text_tool_box",
                  }}
                  arrow={false}
                >
                  <Dropdown
                    menu={{
                      items: [
                        {
                          key: "0",
                          label: "单张添加文字",
                          icon: (
                            <img
                              src={ie_text_option_01_png}
                              style={{ width: 13 }}
                            />
                          ),
                        },
                        imageList?.length > 1
                          ? {
                              key: "1",
                              label: "批量添加文字",
                              icon: (
                                <img
                                  src={ie_text_option_02_png}
                                  style={{ width: 13 }}
                                />
                              ),
                            }
                          : null,
                      ].filter(Boolean),
                      onClick: ({ key }) => {
                        addTextFn({
                          isBatch: key === "1",
                        });

                        if (isOpenMap?.isTextTool) return;
                        handleModalFn("isTextTool", () => {
                          onToggleModalClick("isTextTool", true);
                        });
                      },
                    }}
                    placement="bottom"
                    arrow
                    trigger={["click"]}
                    onOpenChange={(open) => {
                      onToggleActiveClick("isTextActive", open);

                      if (open) {
                        handleModalFn("-1", () => {
                          discardActiveObjectFn();
                        });
                      }
                    }}
                  >
                    <div>
                      <MyTooltipButton
                        tooltipProps={{
                          title: "添加文字",
                        }}
                      >
                        <Icon
                          component={WenziIcon}
                          style={{
                            color: !isActiveMap?.isTextActive
                              ? "#676C75"
                              : "#1664FF",
                            fontSize: 16,
                          }}
                        />
                      </MyTooltipButton>
                    </div>
                  </Dropdown>
                </MyTooltip>

                <Dropdown
                  menu={{
                    items: [
                      {
                        key: 15,
                        label: "顺时针旋转15度",
                        icon: (
                          <img
                            src={ie_rotate_option_01_png}
                            style={{ width: 13 }}
                          />
                        ),
                      },
                      {
                        key: -15,
                        label: "逆时针旋转15度",
                        icon: (
                          <img
                            src={ie_rotate_option_02_png}
                            style={{ width: 13 }}
                          />
                        ),
                      },
                    ],
                    onClick: ({ key }) => {
                      rotateCanvasFn(Number(key));
                    },
                  }}
                  placement="bottom"
                  arrow
                  trigger={["click"]}
                  onOpenChange={(open) => {
                    onToggleActiveClick("isRotateActive", open);

                    if (open) {
                      handleModalFn("-1", () => {
                        discardActiveObjectFn();
                      });
                    }
                  }}
                >
                  <div>
                    <MyTooltipButton
                      tooltipProps={{
                        title: "旋转",
                      }}
                    >
                      <Icon
                        component={XuanzhuanIcon}
                        style={{
                          color: !isActiveMap?.isRotateActive
                            ? "#676C75"
                            : "#1664FF",
                          fontSize: 14,
                        }}
                      />
                    </MyTooltipButton>
                  </div>
                </Dropdown>

                <Dropdown
                  menu={{
                    items: [
                      {
                        key: "0",
                        label: "单张一键重置",
                        icon: (
                          <img
                            src={ie_reset_option_01_png}
                            style={{ width: 13 }}
                          />
                        ),
                      },
                      imageList?.length > 1
                        ? {
                            key: "1",
                            label: "批量一键重置",
                            icon: (
                              <img
                                src={ie_reset_option_02_png}
                                style={{ width: 13 }}
                              />
                            ),
                          }
                        : null,
                    ].filter(Boolean),
                    onClick: ({ key }) => {
                      batchResetCanvasFn(key === "1");
                    },
                  }}
                  placement="bottom"
                  arrow
                  trigger={["click"]}
                  onOpenChange={(open) => {
                    onToggleActiveClick("isResetActive", open);

                    if (open) {
                      handleModalFn("-1", () => {
                        discardActiveObjectFn();
                      });
                    }
                  }}
                >
                  <div>
                    <MyTooltipButton
                      tooltipProps={{
                        title: "一键重置",
                      }}
                    >
                      <Icon
                        component={YijianchongzhiIcon}
                        style={{
                          color: !isActiveMap?.isResetActive
                            ? "#676C75"
                            : "#1664FF",
                          fontSize: 15,
                        }}
                      />
                    </MyTooltipButton>
                  </div>
                </Dropdown>

                <MyTooltip title="撤销">
                  <ToolButton onClick={undo}>
                    <Icon
                      component={ChexiaoIcon}
                      style={{
                        color: "#676C75",
                        fontSize: 16,
                        marginBottom: 2,
                      }}
                    />
                  </ToolButton>
                </MyTooltip>

                <MyTooltip title="恢复">
                  <ToolButton onClick={redo}>
                    <Icon
                      component={HuifuIcon}
                      style={{
                        color: "#676C75",
                        fontSize: 16,
                        marginBottom: 2,
                      }}
                    />
                  </ToolButton>
                </MyTooltip>
              </Space>
            </Flex>
          </div>
        </Row>
        <Row
          style={{
            height: window?.innerHeight - 60,
          }}
        >
          <Col
            className="image-list-box"
            style={{ display: imageList.length == 1 ? "none" : "block" }}
          >
            <div className="list-box">
              <div className="image-item-list">
                {imageList.length > 1 &&
                  imageList.map((v: any) => {
                    const imgUrl = cacheImgRef?.current?.[v?.fileId] || "";
                    const img_name = v?.name || "-";
                    return (
                      <div
                        key={v?.fileId}
                        onClick={() => {
                          if (v?.fileId === activeImage?.fileId) return;

                          handleModalFn("-1", () => {
                            discardActiveObjectFn();

                            setActiveImage(v);
                          });
                        }}
                        className={`image-item ${
                          activeImage.fileId == v.fileId ? "active-image" : ""
                        }`}
                      >
                        <img src={imgUrl} />

                        <div className="image-item__info">
                          <Tooltip title={img_name} placement="topLeft">
                            <Typography.Paragraph
                              ellipsis
                              className="image-item__info--top"
                            >
                              {img_name}
                            </Typography.Paragraph>
                          </Tooltip>
                        </div>
                      </div>
                    );
                  })}
              </div>
            </div>
          </Col>
          <Col
            className={imageList.length > 1 ? "list-content" : "only-content"}
            onClick={(e) => {
              const className = String((e?.target as any)?.className || "");
              if (className.includes("image_editor_canvas")) return;

              discardActiveObjectFn();
            }}
          >
            <Flex
              align="center"
              className="list-content__crop"
              style={{
                display: activeImage?.cropInfo?.cropIng ? "flex" : "none",
              }}
              onClick={(e) => {
                e.preventDefault();
                e.stopPropagation();
              }}
            >
              <Flex gap={6} align="center" className="list-content__crop--left">
                <div className="list-content__crop--left__text">裁剪尺寸</div>

                <MyTooltip title="宽度">
                  <InputNumber
                    value={activeImage?.cropInfo?.cropWidth}
                    min={10}
                    precision={0}
                    prefix="W"
                    onBlur={(e) => {
                      onCropSizeChange(e?.target?.value, "w");
                    }}
                    onPressEnter={(e: Record<string, any>) => {
                      onCropSizeChange(e?.target?.value, "w");
                    }}
                    controls={false}
                  />
                </MyTooltip>

                {cropRatio && cropRatio !== "0" ? (
                  <MyTooltip title={cropRatio ? "解绑比例" : "约束比例"}>
                    <ToolButton
                      onClick={() => {
                        const val = activeImage?.cropInfo?.cropRatio;
                        setCropRatio(cropRatio ? "" : val);
                      }}
                    >
                      <Icon
                        component={GuanlianIcon}
                        style={{
                          color: !cropRatio ? "#676C75" : "#1664FF",
                          fontSize: 14,
                        }}
                      />
                    </ToolButton>
                  </MyTooltip>
                ) : null}

                <MyTooltip title="高度">
                  <InputNumber
                    value={activeImage?.cropInfo?.cropHeight}
                    min={10}
                    precision={0}
                    prefix="H"
                    onBlur={(e) => {
                      onCropSizeChange(e?.target?.value, "h");
                    }}
                    onPressEnter={(e: Record<string, any>) => {
                      onCropSizeChange(e?.target?.value, "h");
                    }}
                    controls={false}
                  />
                </MyTooltip>
              </Flex>

              <div className="list-content__crop--line" />

              <div className="list-content__crop--btn">
                <ToolButton
                  onClick={() => {
                    onCropConfirmClick();
                  }}
                >
                  <img
                    src={edit_icon02_png}
                    className="list-content__crop--btn__icon02"
                  />
                </ToolButton>

                <ToolButton
                  onClick={() => {
                    onCropCancelClick();
                  }}
                >
                  <img
                    src={edit_icon03_png}
                    className="list-content__crop--btn__icon03"
                  />
                </ToolButton>
              </div>
            </Flex>

            {imageList.map((v: any) => {
              return (
                <div
                  className="content"
                  key={v.id}
                  style={{
                    display: activeImage.fileId == v.fileId ? "flex" : "none",
                  }}
                >
                  <div className="editor-area">
                    <canvas
                      ref={v?.canvasRef}
                      className="image_editor_canvas"
                    />
                  </div>
                </div>
              );
            })}

            <div className="ie_image_des">
              <Input.TextArea
                className="ie_image_des__input"
                placeholder="请输入图片描述"
                variant="borderless"
                value={imgDesValue}
                maxLength={1000}
                showCount={false}
                onChange={(e) => {
                  isUpdateDesc.current = true;

                  setImgDesMap((prevState) => {
                    return {
                      ...prevState,
                      [activeImage?.id]: e?.target?.value || "",
                    };
                  });
                }}
              />

              <Flex
                align="center"
                justify="space-between"
                className="ie_image_des__bottom"
              >
                <Button
                  icon={<CopyIcon />}
                  type="text"
                  onClick={() => onCopyClick(imgDesValue)}
                >
                  复制文本
                </Button>

                <span className="ie_image_des__bottom--num">
                  {imgDesNum}/1000
                </span>
              </Flex>
            </div>
          </Col>
        </Row>
      </div>

      <div
        style={{
          fontSize: 12,
          position: "fixed",
          zIndex: 9,
          width: "max-content",
          display: isOpenMap?.isAngle ? "block" : "none",
          ...modalInfo?.position,
        }}
      >
        <Tag color="rgba(145, 193, 255, 0.85)">
          {modalInfo?.angleText || "-"}
        </Tag>
      </div>
    </Spin>
  );
}
